외주로 위치정보를 많이 쓰는 앱의 API 서버를 제작 중이다. 이와 관련해서 요즘 하고 있는 설계 고민들을 적어본다.
데이터베이스 선택
위치정보를 많이 쓴다면 2차원 인덱스를 통해 KNN Query 등의 위치정보 관련 쿼리를 쉽게 할 수 있는 MongoDB가 편하다. 그러니까 MongoDB 하나만 쓰고 RDBMS는 쓰지 말까? 아니면 그냥 Django 모델은 그대로 쓰고, MongoDB는 위치정보 인덱스로만 쓸까? Django 모델의 편리함을 그냥 버리긴 아깝다. 아니면 OpenGIS를 지원하는 PostgreSQL을 쓰면 다 해결되려나?
고민 끝에 일단 MongoDB를 전면적으로 쓰기로 결정했다. 이미 채팅촌에서 써본 경험이 있었는데 괜찮았고, PostGIS는 사용하기 그리 편하지 않았다. MongoDB를 위치정보 인덱스로만 쓰고 Django 모델을 주로 쓰는 접근법도 해봤는데, 나름 괜찮긴 하지만 조금 더 MongoDB를 깊이 써보고 싶었다.
모델 레이어
MongoDB를 쓰기로 했으니 파이썬에서 어떻게 사용할지를 결정해야 했다. 크게 방법은 세 가지. pymongo를 그대로 쓰는 것, MongoKit, MongoEngine. 셋 다 나쁜 방법은 아니지만, 아무래도 MVC에서 모델 중심으로 가려면 pymongo를 그대로 쓰긴 좀 그렇고 뭔가 wrapping을 하긴 해야 한다. MongoEngine은 저번에 써봤는데, Django model을 닮은 게 그다지 맘에 들진 않았다. MongoDB로 계속 개발을 하려면 아무래도 MongoDB의 쿼리 문법에 익숙해져야 하므로 그걸 그대로 쓰는 게 유익하다. MongoEngine이 Django랑 억지로나마 연동이 되긴 하지만, Admin 연동이 제대로 안되는 이상 별로 큰 이득도 아니다. 그래서 MongoEngine은 시도도 안해보고 MongoKit으로 넘어왔다. 근데 MongoKit도 생각보다 불편하다. 가장 불편한 건 collection을 wrapping해서 관리해주지 않고 들고 다녀야 한다는 것이다. MongoKit에서의 document 생성은 다음처럼 database와 collection을 가져온 다음 거기서 모델 객체를 생성해야 한다.
connection.database.collection.MyDocument()
왜 MyDocument()를 바로 쓰는 걸 지원하지 않는지 이해할 수 없다. database와 collection을 미리 지정해두면 이 둘은 생략할 수 있지만, 그래도 connection에 붙여서 써야 한다. 쿼리도 마찬가지다. 별 거 아닐지 모르지만 그냥 이게 맘에 안 들어서 버리기로 했다. 그래서 그냥 예전에 내가 간단하게 만들어 둔 MongoDB model wrapper를 계속 쓰기로 했다. 이름도 붙였다. MongoDB Model을 줄여서 momo.
스키마 문서화
외주다보니 스키마 문서화가 필요하다. Django 모델로 작성했으면 그냥 django-extensions의 graph_models 써서 다이어그램 넘겨버리면 그만이겠지만, momo는 그런 거 없다. 그리고 MongoDB는 schemaless가 기본이다보니 스키마를 정의하는 일이 부가 작업이 된다. 여기서 고민에 빠졌다. 따로 코드로 스키마를 정의할 필요가 없으니 그냥 수동으로 다이어그램만 그려서 관리할까, 아니면 그래도 스키마가 코드에 정의되는 게 명시성도 좋고 나중에 validation 붙이기도 좋으니 코드에 정의하고 다이어그램은 자동으로 뽑아낼까.
별도 문서화보다는 아무래도 코드에 정의를 하고 싶다. 코드에 정의를 하면 나도 개발을 하면서 계속 참조할 수 있어 이득을 얻기 때문이다. 사실 클라이언트에서 꼭 다이어그램까지 원하는 것은 아니기 때문에 스키마 정의한 문법을 그대로 보내줘도 될 것 같긴 하다. 다만 조금 세련된 문법이면서, 나중에 programmable하게 처리할 수 있는 구조이기만 하면 될 듯.
스키마를 코드에서 정의한다면 MongoKit을 베낄까, MongoEngine을 베낄까도 고민이다. MongoKit이 간결하고 시각적이지만, MongoEngine은 여러 가지 속성을 줄 수 있어서 확장성이 좋다. 문법 정의할 때 파이썬이 인터프리터 언어라는 점도 은근히 귀찮다. 클래스 정의할 때 자신을 참조하게 만들 수가 없기 때문이다. method 안에서는 참조할 수 있지만 클래스 레벨로 정의할 때는 참조할 수가 없어서 Django 모델도 문자열이나 'Self' 같은 문법을 쓴다. 아직 이 부분은 고민 중.
REST API 프레임워크
일단 django를 쓰긴 할 텐데, REST API는 뭘로 만들까? 이전까지는 그냥 내가 만든 djangox-route를 써서 라우팅만 처리하고, JSONResponse 같은 걸 또 붙여서 대충 썼다. PUT, DELETE 따윈 지원하지 않지만 내 프로젝트라면 아마 또 그렇게 할 듯. 하지만 외주다보니 좀더 제대로(?) 만들어야 하고, 또 좀더 널리 알려진 프레임워크를 쓰는 게 좋을 듯 하다. 그래서 django-rest-framework와 tastypie를 비교하기 시작했다.
둘다 예제가 너무 Django model 연동 중심으로 되어 있다. 하지만 난 Django 모델을 안 쓰기로 한지라, 별로 도움이 안된다. 모델을 바꿔 끼우려고 해보니 tastypie는 상속해야 하는 메서드들이 좀 이상하다. get_object_list와 obj_get_list라는 어이 없는 네이밍을 해놨다. 확장하려고 문서를 찾아보니 문서도 좀 부실하다. 그래서 잠깐 django-rest-framework 문서를 더 보는데, 일단 문서가 좀더 풍부하고, browsable api가 있어서 이게 더 편할 것 같았다. View와 Serializer를 API마다 세트로 작성해야하는 것처럼 보여서 조금 꺼려지긴 했지만, 아마도 Serializer는 어떻게든 하나로 할 수 있는 방법이 있겠지. 그래서 일단 django-rest-framework로 왔다. 생각보다 쉽게 연동이 되서 Browserable API에서 JSON으로 POST하고 조회하고 하는 건 잘 된다. 일단 API 작성까지는 이게 괜찮은 것 같다.
그런데 또 하나 중요한 건 API 문서화다. 요즘 REST API는 executable documentation이 대세가 되어가고 있고, django-rest-framework의 Browsable API도 그걸 지원한다. 그런데, 이게 좀 제한적이다. 문서화 기능도 적고, list에 GET 요청을 할 때 파라미터를 줄 수 있는 UI는 없는 것 같아보인다. POST야 그냥 JSON으로만 해도 되서 별 상관은 없지만 필터링 파라미터는 필요한데 아직 어떻게 하는지 모르겠다.
그래서 documenting-your-api라는 문서를 봤더니 자체 Browsable API의 문서화보다 오히려 django-rest-swagger를 추천한다. 이건 swagger integration인데, 이것도 생각보다 쉽게 붙었다. 그런데, 클래스 레벨의 docstring만 파라미터 인식을 하고, 메서드 레벨로는 안되는 것 같다. 한글도 문제가 있는지 에러를 낸다. 하지만, 일단 UI 자체에는 필요한 기능이 다 담겨 있는 것 같다. 일단은 이걸로 좀더 파볼 듯. 처음 원했던 것처럼 손 안대고 코 푸는 건 아직 안되는 것 같다.